using System;
using System.IO;
using System.Linq;
using KineBall.Rendering;
using Microsoft.Research.Kinect.Audio;
using Microsoft.Speech.AudioFormat;
using Microsoft.Speech.Recognition;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;


namespace KineBall.GameLogic
{
    public class GameState
    {
        public GameField gameField;
        public Shield redShieldRight;
        public Shield redShieldLeft;
        public Shield blueShield;
        public Ball ball;
        public GameMessageState gameMessageState;
        public GameFlow flow;
    }

    public struct GameMessageState
    {
        public Vector2 posFromScreenCenter;
        public uint countDown;
        public float scale;
        public Color color;
    }

    public enum GameFlow
    {
        CountDown,
        StartingMatch,
        PlayingMatch,
        BluePoint,
        RedPoint,
        FinishingGame,
        Pause
    }

    public class GameUpdater : Microsoft.Xna.Framework.GameComponent
    {
        const float GAME_FIELD_WIDTH = 4.0f;
        const float GAME_FIELD_HEIGHT = 2.5f;
        const float GAME_FIELD_DEPTH = 9.0f;
        const float ACCELERATION = 1.5f;

        public Action returnToMainMenu;

        private RendererState rendererState;

        GameState state;

#if NOKINECT
        Vector2 cursorPosition;
#endif
        TrackedSkeleton _kinect;

        float matchElapsedTime;
        float enemyTime = 0.0f;
        float watched_time = 0.4f;
        float messageTimer = 0.0f;
        float flashingTimer = 0f;
        uint flashingCount = 0;

        CollisionManager collisionManager;

        public GameUpdater(Game game)
            : base(game)
        {
            state = new GameState();

            state.ball = new Ball();

            state.blueShield = new Shield();
            state.blueShield.Height = GAME_FIELD_HEIGHT / 4;
            state.blueShield.Width = GAME_FIELD_WIDTH / 4;
            state.blueShield.Velocity = Vector3.Zero;

            state.redShieldRight = new Shield();
            state.redShieldRight.Height = GAME_FIELD_HEIGHT / 3;
            state.redShieldRight.Width = GAME_FIELD_WIDTH / 4;
            state.redShieldRight.Velocity = new Vector3(4f, 4f, 1f);

            state.redShieldLeft = new Shield();
            state.redShieldLeft.Height = GAME_FIELD_HEIGHT / 3;
            state.redShieldLeft.Width = GAME_FIELD_WIDTH / 4;
            state.redShieldLeft.Velocity = new Vector3(4f, 4f, 1f);

            state.blueShield.position = new Vector3(0f, 0f, -(GAME_FIELD_DEPTH - 1f) * 0.5f);
            state.redShieldLeft.position = new Vector3(0f, 0f, (GAME_FIELD_DEPTH - 1f) * 0.5f);
            state.redShieldRight.position = new Vector3(0f, 0f, (GAME_FIELD_DEPTH - 1f) * 0.5f);

            state.gameField = new GameField(Vector3.Zero, GAME_FIELD_WIDTH, GAME_FIELD_HEIGHT, GAME_FIELD_DEPTH);

            state.flow = new GameFlow();

            InitializeCountDown();

            _kinect = (TrackedSkeleton)(game.Services.GetService(typeof(TrackedSkeleton)));

            game.Services.AddService(typeof(GameState), state);

        }

        private void InitializeCountDown()
        {
            state.ball.Position = new Vector3(0f, 0f, -(GAME_FIELD_DEPTH - 1f) * 0.5f);
            state.ball.Direction = Vector3.Backward;
            state.ball.Speed = 4f;

            state.gameMessageState.posFromScreenCenter = Vector2.Zero;
            state.gameMessageState.scale = 0.5f;
            state.gameMessageState.countDown = 3;
            state.gameMessageState.color = Color.White;

            state.flow = GameFlow.CountDown;
        }

        private void InitializeStartingMessage()
        {
            state.gameMessageState.scale = 1f;
        }

        private void InitializeFinishingMessage()
        {
        }

        private void InitializeNewMatch()
        {
            state.ball.Position = Vector3.Zero;
            state.ball.Direction = Vector3.Forward;
            state.ball.Speed = 4.0f;

            matchElapsedTime = 0.0f;

            state.flow = GameFlow.PlayingMatch;
        }

#if !NOKINECT
        KinectAudioSource kinectSource;
        SpeechRecognitionEngine speechEngine;
        Stream stream;
        string RecognizerId = "SR_MS_en-US_Kinect_10.0";
        GameFlow old_gf;
        private void InitializeSpeechRecognition()
        {
            kinectSource = new KinectAudioSource();

            kinectSource.FeatureMode = true;
            kinectSource.AutomaticGainControl = false;
            kinectSource.SystemMode = SystemMode.OptibeamArrayOnly;

            var rec = (from r in SpeechRecognitionEngine.InstalledRecognizers() where r.Id == RecognizerId select r).FirstOrDefault();

            speechEngine = new SpeechRecognitionEngine(rec.Id);

            var choices = new Choices();
            choices.Add("pause");
            choices.Add("start");

            GrammarBuilder gb = new GrammarBuilder();
            gb.Culture = rec.Culture;
            gb.Append(choices);

            var g = new Grammar(gb);

            speechEngine.LoadGrammar(g);
            speechEngine.SpeechHypothesized += new EventHandler<SpeechHypothesizedEventArgs>(sre_SpeechHypothesized);
            speechEngine.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(sre_SpeechRecognized);
            speechEngine.SpeechRecognitionRejected += new EventHandler<SpeechRecognitionRejectedEventArgs>(sre_SpeechRecognitionRejected);

            stream = kinectSource.Start();

            speechEngine.SetInputToAudioStream(stream,
                          new SpeechAudioFormatInfo(
                              EncodingFormat.Pcm, 16000, 16, 1,
                              32000, 2, null));


            speechEngine.RecognizeAsync(RecognizeMode.Multiple);
        }

        void sre_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
        }

        void sre_SpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
        {
        }

        void sre_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            if (state.flow != GameFlow.Pause)
                old_gf = state.flow;
            
            if (e.Result.Text == "start")
            {
                state.gameMessageState.color = Color.Cyan;
                state.flow = old_gf;
                state.gameMessageState.scale = 1.0f;
                this.Enabled = true;
            }
            else if (e.Result.Text == "pause")
            {
                state.gameMessageState.color = Color.Cyan;
                state.flow = GameFlow.Pause;
                state.gameMessageState.scale = 1.0f;
                this.Enabled = false;
            }
        }
#endif

        public override void Initialize()
        {
            rendererState = (RendererState)Game.Services.GetService(typeof(RendererState));
            collisionManager = new CollisionManager(state);

#if NOKINECT
            // InitializeSpeechRecognition();
#endif

            base.Initialize();
        }

        protected override void Dispose(bool disposing)
        {
            Game.Services.RemoveService(typeof(GameState));
            base.Dispose(disposing);
        }

        public override void Update(GameTime gameTime)
        {
            switch (state.flow)
            {
                case GameFlow.CountDown:
                    UpdateCountDown(gameTime);
                    UpdateShields();
                    break;
                case GameFlow.StartingMatch:
                    UpdateStartMessage(gameTime);
                    UpdateShields();
                    break;
                case GameFlow.PlayingMatch:
                    UpdateShields();
                    UpdateMatch(gameTime);
                    break;
                case GameFlow.BluePoint:
                    UpdateShields();
                    UpdatePointMessage(gameTime);
                    break;
                case GameFlow.RedPoint:
                    UpdateShields();
                    UpdatePointMessage(gameTime);
                    break;
                case GameFlow.FinishingGame:
                    UpdateWinLose(gameTime);
                    break;
            }

            base.Update(gameTime);
        }

        private void UpdatePointMessage(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
            messageTimer += dt;

            if (state.flow == GameFlow.RedPoint)
            {
                state.gameMessageState.color = new Color(255, 30, 30);
            }
            else
            {
                state.gameMessageState.color = Color.Cyan;
            }

            if (messageTimer > 2000.0f)
            {
                messageTimer = 0.0f;
                InitializeCountDown();
            }
        }

        private void UpdateCountDown(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;


            if (state.gameMessageState.countDown > 0)
            {
                state.ball.Position += state.ball.Direction * state.ball.Speed * dt;
                state.gameMessageState.scale += 0.005f;

                if (state.ball.Position.Z > (GAME_FIELD_DEPTH - 1f) * 0.5f)
                {
                    state.gameMessageState.countDown--;
                    state.ball.Position = new Vector3(0f, 0f, -(GAME_FIELD_DEPTH - 1f) * 0.5f);
                    state.gameMessageState.scale = 0.5f;
                }
            }
            else
            {
                state.flow = GameFlow.StartingMatch;
                InitializeStartingMessage();
            }
        }

        private void UpdateStartMessage(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
            bool changeColor = false;

            flashingTimer += dt;

            if (flashingCount > 5)
            {
                if (flashingTimer >= 0.05f) changeColor = true;

                if (flashingCount > 15)
                {
                    flashingCount = 0;
                    flashingTimer = 0;
                    InitializeNewMatch();
                }
            }
            else
            {
                if (flashingTimer >= 0.2f) changeColor = true;
            }

            if (changeColor)
            {
                if (state.gameMessageState.color == Color.Cyan)
                    state.gameMessageState.color = new Color(255, 30, 30);
                else
                    state.gameMessageState.color = Color.Cyan;

                flashingTimer = 0f;
                flashingCount++;
            }
        }

        private void UpdateWinLose(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
            bool changeColor = false;

            flashingTimer += dt;

            if (flashingCount > 5)
            {
                if (flashingTimer >= 0.05f) changeColor = true;

                if (flashingCount > 15)
                {
                    flashingCount = 0;
                    flashingTimer = 0;

                    if (returnToMainMenu != null)
                    {
                        this.Enabled = false;
                        returnToMainMenu();
                    }
                }
            }
            else
            {
                if (flashingTimer >= 0.2f) changeColor = true;
            }

            if (changeColor)
            {
                if (state.gameMessageState.color == Color.Cyan)
                    state.gameMessageState.color = new Color(255, 30, 30);
                else
                    state.gameMessageState.color = Color.Cyan;

                flashingTimer = 0f;
                flashingCount++;
            }
        }

        private void UpdateShields()
        {
#if NOKINECT
            float x = Mouse.GetState().X;
            float y = Mouse.GetState().Y;

            cursorPosition = new Vector2(x, y);

            Vector3 mouseIn3D = Game.GraphicsDevice.Viewport.Unproject(new Vector3(cursorPosition.X, cursorPosition.Y, 0.0f), rendererState.camera.Projection, rendererState.camera.View, Matrix.Identity);
            mouseIn3D.Z = (GAME_FIELD_DEPTH - 1f) * 0.5f;

            state.redShieldRight.position = new Vector3(mouseIn3D.X, mouseIn3D.Y, (GAME_FIELD_DEPTH - 1f) * 0.5f);
            state.redShieldLeft.position = new Vector3(mouseIn3D.X, mouseIn3D.Y, (GAME_FIELD_DEPTH - 1f) * 0.5f);
#else
            _kinect.rightPosition3D *= state.redShieldRight.Velocity;
            _kinect.leftPosition3D *= state.redShieldLeft.Velocity;

            _kinect.rightPosition3D.X = MathHelper.Clamp(_kinect.rightPosition3D.X, -GAME_FIELD_WIDTH * 0.5f, GAME_FIELD_WIDTH * 0.5f);
            _kinect.rightPosition3D.Y = MathHelper.Clamp(_kinect.rightPosition3D.Y, -GAME_FIELD_HEIGHT * 0.5f, GAME_FIELD_HEIGHT);

            _kinect.leftPosition3D.X = MathHelper.Clamp(_kinect.leftPosition3D.X, -GAME_FIELD_WIDTH * 0.5f, GAME_FIELD_WIDTH * 0.5f);
            _kinect.leftPosition3D.Y = MathHelper.Clamp(_kinect.leftPosition3D.Y, -GAME_FIELD_HEIGHT * 0.5f, GAME_FIELD_HEIGHT);

            state.redShieldRight.position = new Vector3(_kinect.rightPosition3D.X, _kinect.rightPosition3D.Y, (GAME_FIELD_DEPTH - 1f) * 0.5f);
            state.redShieldLeft.position = new Vector3(_kinect.leftPosition3D.X, _kinect.leftPosition3D.Y, (GAME_FIELD_DEPTH - 1f) * 0.5f);
#endif
        }

        private void UpdateMatch(GameTime gameTime)
        {
            // Time update
            #region timeUpdate
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
            matchElapsedTime += dt;
            #endregion

            // Ball update
            state.ball.UpdatePosition(dt, matchElapsedTime);

            // Enemy update
            #region shields update
            state.blueShield.Velocity = (state.ball.Position - state.blueShield.position) * new Vector3(1, 1, 0) * (3f + watched_time);
            state.blueShield.position += state.blueShield.Velocity * dt;
            enemyTime += dt;

            if (enemyTime > 1f)
            {
                watched_time += 0.02f;
                enemyTime = 0.0f;
            }
            #endregion

            // Collision checking and match flow updating
            state.flow = collisionManager.UpdateAndCheckPoint(rendererState.sphereBounding);

            if (state.flow == GameFlow.BluePoint) ++state.blueShield.Score;
            if (state.flow == GameFlow.RedPoint) ++state.redShieldRight.Score;

            if ((state.redShieldRight.Score == 4) || (state.blueShield.Score == 4))
                state.flow = GameFlow.FinishingGame;

            // InitializeCountDown();   
        }
    }
}
